let activeEffect;

class Dep {
  subscribers = new Set();
  depend() {
    if (activeEffect) {
      this.subscribers.add(activeEffect);
    }
  }
  notify() {
    this.subscribers.forEach((effect) => effect());
  }
}

function watchEffect(effect) {
  activeEffect = effect;
  effect();
  activeEffect = null;
}

function observerArray(raw, key, dep) {
  const arrayMethods = Object.create(Object.prototype);

  ["push", "pop", "shift", "unshift", "splice", "sort", "reverse"].forEach(
    function (method) {
      let original = Array.prototype[method];

      Object.defineProperty(arrayMethods, method, {
        enumerable: true,
        configurable: true,
        value(...args) {
          original.apply(this, args);
          dep.notify();
        },
      });
    }
  );

  Object.setPrototypeOf(raw[key], arrayMethods);
}

function reactive(raw) {
  // use Object.defineProperty
  // 1. iterate over the existing keys
  Object.keys(raw).forEach((key) => {
    // 2. for each key: create a corresponding dep
    const dep = new Dep();

    // 3. rewrite the property into getter/setter
    let realValue = raw[key];

    if (Array.isArray(realValue)) {
      observerArray(raw, key, dep)
    }

    Object.defineProperty(raw, key, {
      get() {
        // 4. call dep methods inside getter/setter
        dep.depend();

        return realValue;
      },
      set(newValue) {
        realValue = newValue;

        dep.notify();
      },
    });
  });

  return raw;
}

// usage -----------------------
const state = reactive({
  count: 0,
  name: "ling",
  schools: [],
});

watchEffect(() => {
  const { count, name, schools } = state;
  console.log(count, name, schools);
});

state.count++;
state.schools.push("你好");
state.schools.pop();
